package org.kwisatz.nodistun;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.Proxy.Type;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main
{
	private static class EchoServer extends Thread
	{
		private int port;
		
		EchoServer(int port)
		{
			this.port=port;
		}
		
		public void run()
		{
			try
			{
				ServerSocket server=new ServerSocket(port);
				Socket s = server.accept();
				InputStream in=s.getInputStream();
				OutputStream out=s.getOutputStream();
				while(true)
				{
					int input = in.read();
					if(input==42)
						out.write(69);
					else
						throw new IllegalStateException();
				}
			}
			catch(IOException ex)
			{
				ex.printStackTrace();
			}
		}		
	}

	private static class Tunnel extends Thread
	{
		private int localPort;
		private String remoteHost;
		private int remotePort;
		private Socket localSocket, remotSocket;
		Tunnel(int localPort, String remoteHost, int remotePort)
		{
			this.localPort=localPort;
			this.remoteHost=remoteHost;
			this.remotePort=remotePort;
		}

		public void run()
		{
			try
			{
				ServerSocket tunnel=new ServerSocket(localPort);
				while(true)
				{
					localSocket=tunnel.accept();
					remotSocket=new Socket(remoteHost, remotePort);
					System.out.println("Restarting tunnel");
					new PipeStream("Tunnel->", localSocket.getInputStream(), remotSocket.getOutputStream()).start();
					new PipeStream("Tunnel<-",remotSocket.getInputStream(), localSocket.getOutputStream()).start();
					Thread.sleep(10000);
					disconnect();
				}
			}
			catch(Exception ex)
			{
				ex.printStackTrace();
			}			
		}
		
		public void disconnect() throws IOException
		{
			localSocket.close();
			remotSocket.close();
		}
	}

	private static void client(Proxy proxy, String host, int port) throws IOException, InterruptedException
	{
		Socket s=new Socket(proxy);
		s.connect(new InetSocketAddress(host, port));
		InputStream in = s.getInputStream();
		OutputStream out = s.getOutputStream();
		while(true)
		{
			out.write(42);
			System.out.println(in.read());
			Thread.sleep(500);
		}
	}

	static public void main(String[] args)
	{
		try
		{
			Logger.getLogger("").setLevel(Level.ALL);
			Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL);
			//10800: local SOCKS proxy server
			//10801: local tunnel
			//10802: remote nodisconnect server
			//10803: remote application server

			new RemoteProxyServer(10802).start();
			new LocalProxyServer(10800, "127.0.0.1", 10801).start();
			
			//Bind the tunnel to the remote proxy server
			Tunnel tunnel = new Tunnel(10801, "127.0.0.1", 10802);
			tunnel.start();
			
			new EchoServer(10803).start();

			Authenticator.setDefault(new Authenticator()
			{
				protected PasswordAuthentication getPasswordAuthentication()
				{
					return new PasswordAuthentication("toto", new char[]{'a','o','x'});
				}
			});
			
			//Connect the client through the local proxy
			Proxy proxy=new Proxy(Type.SOCKS, new InetSocketAddress("127.0.0.1",10800));			
			client(proxy, "127.0.0.1", 10803);
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}
	}
}
